
#include <assert.h>
#include <string.h>

#include "core/olisp_core.h"
#include "core/olisp_mem.h"

/*
 * string hash function
 */
STATIC_INLINE lisp_hash_t calc_hash( const char * src_str, size_t len )
{
    const unsigned char * str = (const unsigned char *)src_str;
	size_t n = len;
	lisp_hash_t h = 0;
	lisp_hash_t g;

	while( n-- )
	{
		h = (h << 4) + *str++;
		if ((g = (h & 0xf0000000)) != 0)
			h ^= g >> 23;
		h &= ~g;
	}

	return h;
}

/*
 * element of an identifiers storage
 * TODO: make it rb-tree, not linked list
 */

typedef struct strpair_t
{
    struct strpair_t *  next;
    lisp_hash_t         hash;
    char                str[1];
} strpair_t;

static strpair_t * strpairs_first = 0;

static const char * get_str( lisp_hash_t hash )
{
    strpair_t * p = strpairs_first;
    while( p )
    {
        if( p->hash == hash )
            return p->str;

        p = p->next;
    }

    return 0;
}

static int add_str( const char * src_str, lisp_hash_t * hash, const char ** dst_str )
{
    size_t str_len = strlen( src_str );
    lisp_hash_t h = calc_hash( src_str, str_len );
    strpair_t * p;

    // check whether this element already exists
    *dst_str = get_str( h );
    if( *dst_str )
    {
        *hash = h;
        return 1;
    }

    // create one another hash pair
    p = lisp_malloc( str_len + sizeof(strpair_t) );
    if( !p )
        return 0;

    p->next = strpairs_first;
    *hash = p->hash = h;
    memcpy( p->str, src_str, str_len + 1 );
    *dst_str = p->str;
    strpairs_first = p;

    return 1;
}

static int id_inst_count = 0;

static void hashes_storage_release()
{
    if( !-- id_inst_count )
    {
        // release strings hashes storage
        strpair_t * p = strpairs_first;
        while( p )
        {
            strpair_t * this_pair = p;
            p = p->next;
            
            lisp_free( this_pair );
        }
    }

    assert( id_inst_count >= 0 );
}

static void hashes_storage_add_ref()
{
    ++ id_inst_count;
}



/*
 * implementation of an identifier's interface
 */

typedef struct id_impl_t
{
    id_obj_vtbl_t * vtbl;
    
    int ref_count;

    lisp_hash_t str_hash;

    const char * str;
} id_impl_t;

static int id_add_ref( id_obj_t * self )
{
    id_impl_t * s = (id_impl_t *)self;
    return ++s->ref_count;
}

static int id_release( id_obj_t * self )
{
    id_impl_t * s = (id_impl_t *)self;
    int res = --s->ref_count;
    
    if( res > 0 )
        return res;

    hashes_storage_release();
    lisp_free( s );
    return 0;
}

static const char * id_as_str( id_obj_t * self )
{
    id_impl_t * s = (id_impl_t *)self;
    return s->str;
}

static lisp_hash_t id_as_hash( id_obj_t * self )
{
    id_impl_t * s = (id_impl_t *)self;
    return s->str_hash;
}

static id_obj_vtbl_t id_obj_vtbl =
{
    LISP_ELEM_ID,
    &id_add_ref,
    &id_release,

    &id_as_str,
    &id_as_hash
};

id_obj_t * create_identifier( const char * id )
{
    lisp_hash_t str_hash;
    id_impl_t * s;
    const char * str;

    if( !add_str(id, &str_hash, &str) )
        return 0;

    hashes_storage_add_ref();

    s = lisp_malloc( sizeof(id_impl_t) );
    if( s )
    {
        s->str_hash = str_hash;
        s->str = str;
        s->ref_count = 1;
        s->vtbl = &id_obj_vtbl;

        return (id_obj_t *)s;
    }

    hashes_storage_release();
    return 0;
}

